package math.region;

import math.math;
import math.vec2;
import math.vec3;

public class Sphere3 implements Volume3, Surface3
{
    
    public vec3 mOrig;
    
    public double mRadius;
    
    
    public Sphere3()
    {
        this.mOrig = new vec3();
        this.mRadius = 0.0;
    }
    
    public Sphere3(
            vec3 aOrig, 
            double aRadius)
    {
        this();
        this.set(aOrig, aRadius);
    }
    
    
    public final void set(
            vec3 aOrig, 
            double aRadius)
    {
        this.mOrig.set(aOrig);
        this.mRadius = aRadius;
    }
    
    public final void set(Sphere3 aOther)
    {
        this.set(aOther.mOrig, aOther.mRadius);
    }
    
    
    public boolean insideVol(vec3 aPoint)
    {
        vec3 toPt = vec3.sub(aPoint, this.mOrig);
        double distSq = toPt.lengthSq();
        double radSq = this.mRadius * this.mRadius;
        return (distSq < radSq);
    }
    
    public boolean onSurface(vec3 aPoint)
    {
        vec3 toPt = vec3.sub(aPoint, this.mOrig);
        double distSq = toPt.lengthSq();
        double radSq = this.mRadius * this.mRadius;
        double diff = (distSq - radSq);
        return (math.abs(diff) < math.EPSILON);
    }
    
    public boolean overlaps(Sphere3 other)
    {
        double sumRad = this.mRadius + other.mRadius;
        double sqSumRad = sumRad * sumRad;
        double sqDistBetw = vec3.distanceSq(this.mOrig, other.mOrig);
        return sqDistBetw <= sqSumRad;
    }
    
    public boolean equals(Object other)
    {
        if (!(other instanceof Sphere3))
        {
            return false;
        }
        Sphere3 soth = (Sphere3) other;
        return (
            soth.mOrig.close(this.mOrig, 1.0e-7) && 
            math.abs(soth.mRadius - this.mRadius) <= 1.0e-7
        );
    }
    
    
    public int hashCode()
    {
        double sum = vec3.dot(this.mOrig, vec3.ONE);
        sum *= this.mRadius;
        long hash = Double.doubleToLongBits(sum);
        return (int) hash;
    }
    
    
    public double surfaceArea()
    {
        return (4.0 * math.PI * this.mRadius * this.mRadius);
    }
    
    
    public vec3 surfaceNorm(
            vec3 aPoint, 
            vec3 aReferencePt)
    {
        // Get the outward normal:
        vec3 norm = vec3.sub(aPoint, this.mOrig);
        norm.norm();
        // Determine which direction, out or in:
        boolean refInside = this.insideVol(aReferencePt);
        if (refInside)
        {
            norm.mul(-1.0);
        }
        // Return the normal in the correct direction:
        return norm;
    }
    
    public vec3 convertParam_World(vec2 aUV)
    {
        double theta = 2.0 * math.PI * aUV.x;
        double phi = math.PI * aUV.y;
        double ct = math.cos(theta);
        double st = math.sin(theta);
        double cp = math.cos(phi);
        double sp = math.sin(phi);
        vec3 world = new vec3(
            this.mRadius * ct * sp, 
            this.mRadius * cp, 
            -this.mRadius * st * sp
        );
        world.add(this.mOrig);
        return world;
    }
    
    public vec2 convertWorld_Param(vec3 aPos)
    {
        vec3 local = vec3.sub(aPos, this.mOrig);
        double u = math.atan(-local.z, local.x) / (2.0 * math.PI);
        if (u < 0)
        {
            u += 1.0;
        }
        double v = math.acos(local.y * local.rlength()) / math.PI;
        vec2 uv = new vec2(u, v);
        return uv;
    }
    
    
    public Surface3 lerp(Surface3 next, double t)
    {
        Sphere3 snext = (Sphere3) next;
        Sphere3 lerp = this.copy();
        lerp.mOrig.lerp(snext.mOrig, t);
        lerp.mRadius = math.lerp(this.mRadius, snext.mRadius, t);
        return lerp;
    }
    
    
    public AABB3 boundingBox()
    {
        vec3 min = vec3.sub(this.mOrig, this.mRadius);
        vec3 max = vec3.add(this.mOrig, this.mRadius);
        return new AABB3(min, max);
    }
    
    
    public Sphere3 copy()
    {
        Sphere3 copy = new Sphere3();
        copy.set(this);
        return copy;
    }
    
    
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append("Sphere3\n   Orig: ");
        sb.append(this.mOrig);
        sb.append("\n   Radius: ");
        sb.append(this.mRadius);
        return sb.toString();
    }
    
}
